package com.ruoyi.web.controller.wx.controller;


import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.ruoyi.common.constant.KaurConstants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.CullenUtils;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.module.domain.Order;
import com.ruoyi.module.service.IFundDetailsService;
import com.ruoyi.module.service.IGoodsService;
import com.ruoyi.module.service.IOrderService;
import com.ruoyi.web.controller.wx.config.CommonWechatPayUrlEnum;
import com.ruoyi.web.controller.wx.config.WechatPayConfig;
import com.ruoyi.web.controller.wx.po.H5Po;
import com.ruoyi.web.controller.wx.po.JsapiPo;
import com.ruoyi.web.controller.wx.po.SubQrCodePo;
import com.ruoyi.web.controller.wx.request.WechatPayRequest;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author kaur
 */
@Slf4j
@RestController
@RequestMapping("/common/pay")
@Api(tags = "商户-普通支付-支付接口(API3)")
public class CommonPayController {

    @Autowired
    private WechatPayConfig wechatPayConfig;
    @Autowired
    private WechatPayRequest wechatPayRequest;
    @Autowired
    private IOrderService orderService;
    @Autowired
    private IGoodsService goodsService;
    @Autowired
    private IFundDetailsService fundDetailsService;

    /**
     * 无需应答签名
     */
    @Autowired
    private CloseableHttpClient wxPayNoSignClient;


    @PostMapping("/transactions/service/subJsapi")
    @ApiOperation(value = "普通支付-jsapi")
    public AjaxResult subJsapi(@Valid @RequestBody JsapiPo po) {
        System.err.println("====================进入支付=====================");
        Order order = orderService.selectOrderById(po.getOrderId());
        String type = "jsapi";
        Map<String, Object> params = new HashMap<>(8);
        params.put("appid", wechatPayConfig.getAppId());
        params.put("mchid", wechatPayConfig.getMchId());
        params.put("description", "小程序购买商品");

        params.put("out_trade_no", order.getOrderNo());

//        JSONObject attachJson = new JSONObject();
//        attachJson.put("orderId", po.getOrderId().toString());
//        String json = JSONObject.toJSONString(attachJson);
        params.put("attach", po.getOrderId().toString());
        params.put("notify_url", wechatPayConfig.getNotifyUrl()+"/common/notify/jsapi");


        Map<String, Object> amountMap = new HashMap<>(4);
        amountMap.put("total", order.getOrderTotalMoney().multiply(new BigDecimal(100)).intValue());
//        amountMap.put("total", order.getOrderTotalMoney());
        amountMap.put("currency", "CNY");
        params.put("amount", amountMap);

        Map<String, Object> payerMap = new HashMap<>(4);
        payerMap.put("openid", po.getOpenId());
        params.put("payer", payerMap);

        Map<String, Object> sceneInfoMap = new HashMap<>(4);
        sceneInfoMap.put("payer_client_ip", "127.0.0.1");
        sceneInfoMap.put("device_id", "127.0.0.1");
        params.put("scene_info", sceneInfoMap);

        String paramsStr = JSON.toJSONString(params);
        log.info("请求参数 ===> {}" + paramsStr);
        String[] split = type.split("_");
        String newType = split[split.length - 1];

        String url = wechatPayConfig.getBaseUrl().concat(CommonWechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat(newType));
        log.info("请求地址 ===> {}" + url);
        String resStr = wechatPayRequest.wechatHttpPost(url, paramsStr);
        Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
        });
        String prepayId = resMap.get("prepay_id").toString();
        long timeMillis = System.currentTimeMillis();
        String timeStamp = timeMillis / 1000 + "";
        String nonceStr = timeMillis + "";
        resMap.put("type", type);
        resMap.put("signMap", getWxPayResultMap(prepayId, timeStamp, nonceStr));
        return AjaxResult.success(resMap);
    }


    @PostMapping("/transactions/service/subQrCode")
    @ApiOperation(value = "二维码支付")
    public AjaxResult subQrCode(@Valid @RequestBody SubQrCodePo po) {
        Order order = orderService.selectOrderById(po.getOrderId());
        try {
            // 配置微信支付参数(商户号、私钥路径、证书序列号、API v3 密钥)
            String keyPemPath = wechatPayConfig.getPrivateKeyContent();
            Config config = new RSAAutoCertificateConfig.Builder()
                    .merchantId(wechatPayConfig.getMchId())
                    .privateKey(keyPemPath)
                    .merchantSerialNumber(wechatPayConfig.getSerialNo())
                    .apiV3Key(wechatPayConfig.getApiV3Key())
                    .build();

            // 构建支付服务
            NativePayService service = new NativePayService.Builder().config(config).build();

            // 构建支付请求
            PrepayRequest request = new PrepayRequest();
            Amount amount = new Amount();
            amount.setTotal(order.getOrderTotalMoney().multiply(new BigDecimal(100)).intValue()); //支付金额  单位：分
            request.setAmount(amount);
            request.setAttach(po.getOrderId().toString());
            request.setAppid(wechatPayConfig.getAccountAppId());//微信服务号 appid
            request.setMchid(wechatPayConfig.getMchId()); //商户号
            request.setDescription(po.getDescription());
            request.setNotifyUrl(wechatPayConfig.getNotifyUrl()+"/common/notify/native"); // 回调地址
//            request.setNotifyUrl("http://82.156.163.74:8080/common/notify/native"); // 回调地址
            request.setOutTradeNo(order.getOrderNo());
            System.err.println("==================== 微信请求参数 =========================\n");
            System.err.println(request);
            System.err.println("\n=============================================\n");
            // 发起支付请求
            PrepayResponse response = service.prepay(request);

            // 返回支付二维码 URL
            System.err.println(response.getCodeUrl());
            return AjaxResult.success().put("codeUrl", response.getCodeUrl());
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("支付请求失败：" + e.getMessage());
        }
    }

    /**
     * 获取证书管理器实例
     *
     * @return return
     */
    @SneakyThrows
    public Verifier getVerifier() {
        log.info("获取证书管理器实例");
        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(wechatPayConfig.getKeyPemPath());
        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(wechatPayConfig.getSerialNo(), privateKey);
        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(wechatPayConfig.getMchId(), privateKeySigner);
        // 使用定时更新的签名验证器，不需要传入证书
        CertificatesManager certificatesManager = CertificatesManager.getInstance();
        certificatesManager.putMerchant(wechatPayConfig.getMchId(),
                wechatPay2Credentials, wechatPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        return certificatesManager.getVerifier(wechatPayConfig.getMchId());
    }

    /**
     * 获取商户的私钥文件
     *
     * @param keyPemPath keyPemPath
     * @return return
     */
    public PrivateKey getPrivateKey(String keyPemPath) {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(keyPemPath);
        if (inputStream == null) {
            throw new ServiceException("私钥文件不存在");
        }
        return PemUtil.loadPrivateKey(inputStream);
    }


    /**
     * 构建签名
     *
     * @param prepayId  prepay_id=up_wx21201855730335ac86f8c43d1889123400
     * @param timestamp 时间戳
     * @param nonceStr  随机字符串
     * @return the wx pay result map
     */
    @SneakyThrows
    public Map<String, Object> getWxPayResultMap(String prepayId, String timestamp, String nonceStr) {
        StringBuilder sb = new StringBuilder();
        // 应用id
        sb.append(wechatPayConfig.getAppId()).append("\n");
        // 支付签名时间戳
        sb.append(timestamp).append("\n");
        // 随机字符串
        sb.append(nonceStr).append("\n");
        // 预支付交易会话ID
        sb.append("prepay_id=").append(prepayId).append("\n");
        // 签名
        Signature sign = Signature.getInstance("SHA256withRSA");
        // 获取商户私钥并进行签名
        PrivateKey privateKey = wechatPayConfig.getPrivateKey(wechatPayConfig.getKeyPemPath());
        sign.initSign(privateKey);
        sign.update(sb.toString().getBytes(StandardCharsets.UTF_8));
        String paySign = Base64.getEncoder().encodeToString(sign.sign());
        Map<String, Object> map = new HashMap<>();
        map.put("appId", wechatPayConfig.getAppId()); // 小程序id
        map.put("timeStamp", timestamp); // 时间戳
        map.put("nonceStr", nonceStr); // 随机字符串
        map.put("package", "prepay_id=" + prepayId); // 预支付交易会话ID
        map.put("signType", "RSA"); // 签名方式
        map.put("paySign", paySign); // 签名
        return map;
    }

    /**
     * 生成带签名支付信息
     *
     * @param timestamp 时间戳
     * @param nonceStr  随机数
     * @param prepayId  预付单
     * @return 支付信息
     */
    @SneakyThrows
    public String appPaySign(String timestamp, String nonceStr, String prepayId) {
        //上传私钥
        PrivateKey privateKey = wechatPayConfig.getPrivateKey(wechatPayConfig.getKeyPemPath());
        String signatureStr = Stream.of(wechatPayConfig.getAppId(), timestamp, nonceStr, prepayId)
                .collect(Collectors.joining("\n", "", "\n"));
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(privateKey);
        sign.update(signatureStr.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    private Map<String, String> paySignMsg(Map<String, Object> map, String type) {
        // 设置签名信息,Native与H5不需要
        if (type.equals(CommonWechatPayUrlEnum.H5.getType()) || type.equals(CommonWechatPayUrlEnum.NATIVE.getType())) {
            return null;
        }
        long timeMillis = System.currentTimeMillis();
        String appId = wechatPayConfig.getAppId();
        String timeStamp = timeMillis / 1000 + "";
        String nonceStr = timeMillis + "";
        String prepayId = map.get("prepay_id").toString();
        String packageStr = "prepay_id=" + prepayId;
        // 公共参数
        Map<String, String> resMap = new HashMap<>();
        // JSAPI、SUB_JSAPI(小程序)
        if (type.equals(CommonWechatPayUrlEnum.JSAPI.getType()) || type.equals(CommonWechatPayUrlEnum.SUB_JSAPI.getType())) {
            resMap.put("nonceStr", nonceStr);
            resMap.put("timeStamp", timeStamp);
            resMap.put("appId", appId);
            resMap.put("package", packageStr);
            // 使用字段appId、timeStamp、nonceStr、package进行签名
            String paySign = appPaySign(timeStamp, nonceStr, prepayId);
            resMap.put("paySign", paySign);
            resMap.put("signType", "HMAC-SHA256");
        }
        // APP
        if (type.equals(CommonWechatPayUrlEnum.APP.getType())) {
            resMap.put("appid", appId);
            resMap.put("partnerid", wechatPayConfig.getMchId());
            resMap.put("prepayid", prepayId);
            resMap.put("package", "Sign=WXPay");
            resMap.put("noncestr", nonceStr);
            resMap.put("timestamp", timeStamp);
            resMap.put("sign", appPaySign(timeStamp, nonceStr, prepayId));
        }
        return resMap;
    }

    @ApiOperation(value = "根据订单号查询订单")
    @GetMapping("/transactions/{orderNo}")
    public Map<String, Object> transactionsByOrderNo(@PathVariable("orderNo") String orderNo) {
        // TODO 如果是扫码支付时，该接口就很有必要，应该前端通过轮询的方式请求该接口查询订单是否支付成功
        log.info("根据订单号查询订单，订单号： {}", orderNo);
        String url = wechatPayConfig.getBaseUrl().concat(CommonWechatPayUrlEnum.ORDER_QUERY_BY_NO.getType().concat(orderNo))
                .concat("?mchid=").concat(wechatPayConfig.getMchId());
        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询订单结果：{}", res);
        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
        });
        String outTradeNo = resMap.get("out_trade_no").toString();
        String appId = resMap.get("appid").toString();
        String mchId = resMap.get("mchid").toString();
        //交易状态，枚举值：SUCCESS：支付成功  REFUND：转入退款 NOTPAY：未支付 CLOSED：已关闭
        // REVOKED：已撤销（仅付款码支付会返回） USERPAYING：用户支付中（仅付款码支付会返回）
        // PAYERROR：支付失败（仅付款码支付会返回）
        String tradeState = resMap.get("trade_state").toString();
        log.info("outTradeNo：" + outTradeNo);
        log.info("appId：" + appId);
        log.info("mchId：" + mchId);
        log.info("tradeState：" + tradeState);
        return resMap;
    }

    @ApiOperation(value = "关闭(取消)订单")
    @PostMapping("/closeOrder/{orderNo}")
    public void closeOrder(@PathVariable("orderNo") String orderNo) {
        // TODO 用于在客户下单后，不进行支付，取消订单的场景
        log.info("根据订单号取消订单，订单号： {}", orderNo);
        String url = String.format(CommonWechatPayUrlEnum.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wechatPayConfig.getBaseUrl().concat(url);
        // 设置参数
        Map<String, String> params = new HashMap<>(2);
        params.put("mchid", wechatPayConfig.getMchId());
        String paramsStr = JSON.toJSONString(params);
        log.info("请求参数 ===> {}" + paramsStr);
        String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
        log.info("请求结果 ===> {}" + res);
    }

    @ApiOperation(value = "申请退款")
    @GetMapping("/refundOrder/{orderNo}")
    public AjaxResult refundOrder(@PathVariable("orderNo") String orderNo, @RequestParam String reasonForCancel) {
        BigDecimal total;
        Order order = orderService.selectOrderByOrderNo(orderNo);
        CullenUtils.validateDataThrowException(order == null, "单号对应订单不存在...");
        total = order.getOrderTotalMoney();

        log.info("根据订单号申请退款，订单号： {}", orderNo);
        String url = wechatPayConfig.getBaseUrl().concat(CommonWechatPayUrlEnum.DOMESTIC_REFUNDS.getType());
        // 设置参数
        Map<String, Object> params = new HashMap<>(2);
        // 订单编号
        params.put("out_trade_no", orderNo);
        // 退款单编号 - 自定义
//        int outRefundNo = new Random().nextInt(999999999);
        String tk = OrderUtils.getOrderNo("tk");
        log.info("退款申请号：{}", tk);
        params.put("out_refund_no", tk + "");


        // 自定义的订单单号字段，用于回调时获取订单信息
//        params.put("order_number", orderNo);  // 这里增加了 "order_number" 字段

        // 退款原因
        params.put("reason", reasonForCancel);
        // 退款通知回调地址
        params.put("notify_url", String.format("%s%s", wechatPayConfig.getNotifyUrl(), "/common/notify/refundNotify"));
        Map<String, Object> amountMap = new HashMap<>();
        //退款金额，单位：分
        amountMap.put("refund", total.multiply(new BigDecimal(100)).intValue());
        //原订单金额，单位：分
        amountMap.put("total", total.multiply(new BigDecimal(100)).intValue());
        //退款币种
        amountMap.put("currency", "CNY");
        params.put("amount", amountMap);
        String paramsStr = JSON.toJSONString(params);
        log.info("请求参数 ===> {}" + paramsStr);
        String res = wechatPayRequest.wechatHttpPost(url, paramsStr);
        log.info("退款结果：{}", res);

        order.setPayStatus(10);
        order.setReasonForCancel(reasonForCancel);
        orderService.updateOrder(order);

        return AjaxResult.success(res);
    }

    @ApiOperation(value = "查询单笔退款信息")
    @GetMapping("/queryRefundOrder/{refundNo}")
    public Map<String, Object> queryRefundOrder(@PathVariable("refundNo") String refundNo) {
        log.info("根据订单号查询退款订单，订单号： {}", refundNo);
        String url = wechatPayConfig.getBaseUrl().concat(CommonWechatPayUrlEnum.DOMESTIC_REFUNDS_QUERY.getType().concat(refundNo));
        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询退款订单结果：{}", res);
        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
        });
        String successTime = resMap.get("success_time").toString();
        String refundId = resMap.get("refund_id").toString();
        //款到银行发现用户的卡作废或者冻结了，导致原路退款银行卡失败，可前往商户平台-交易中心，手动处理此笔退款。
        //枚举值：SUCCESS：退款成功 CLOSED：退款关闭 PROCESSING：退款处理中 ABNORMAL：退款异常
        String status = resMap.get("status").toString();
        //枚举值：ORIGINAL：原路退款 BALANCE：退回到余额 OTHER_BALANCE：原账户异常退到其他余额账户 OTHER_BANKCARD：原银行卡异常退到其他银行卡
        String channel = resMap.get("channel").toString();
        log.info("successTime：" + successTime);
        log.info("channel：" + channel);
        log.info("refundId：" + refundId);
        log.info("status：" + status);
        // TODO 在查询单笔退款信息时，可以再去查询一次订单的状态，保证该订单已经退款完毕了
        return resMap;
    }

    /**
     * 申请交易账单
     *
     * @param billDate 格式yyyy-MM-dd 仅支持三个月内的账单下载申请 ，如果传入日期未为当天则会出错
     * @param billType 分为：ALL、SUCCESS、REFUND
     *                 ALL：返回当日所有订单信息（不含充值退款订单）
     *                 SUCCESS：返回当日成功支付的订单（不含充值退款订单）
     *                 REFUND：返回当日退款订单（不含充值退款订单）
     * @return 结果
     */
    @ApiOperation(value = "申请交易账单")
    @GetMapping("/tradeBill")
    public String tradeBill(@RequestParam("billDate") String billDate, @RequestParam("billType") String billType) {
        log.info("申请交易账单，billDate：{}，billType：{}", billDate, billType);
        String url = wechatPayConfig.getBaseUrl().concat(CommonWechatPayUrlEnum.TRADE_BILLS.getType())
                .concat("?bill_date=").concat(billDate).concat("&bill_type=").concat(billType);
        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询退款订单结果：{}", res);
        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
        });
        return resMap.get("download_url").toString();
    }

    /**
     * @param billDate    格式yyyy-MM-dd 仅支持三个月内的账单下载申请，如果传入日期未为当天则会出错
     * @param accountType 分为：BASIC、OPERATION、FEES
     *                    BASIC：基本账户
     *                    OPERATION：运营账户
     *                    FEES：手续费账户
     * @return 结果
     */
    @ApiOperation(value = "申请资金账单")
    @GetMapping("/fundFlowBill")
    public String fundFlowBill(@RequestParam("billDate") String billDate, @RequestParam("accountType") String accountType) {
        log.info("申请交易账单，billDate：{}，accountType：{}", billDate, accountType);
        String url = wechatPayConfig.getBaseUrl()
                .concat(CommonWechatPayUrlEnum.FUND_FLOW_BILLS.getType())
                .concat("?bill_date=")
                .concat(billDate)
                .concat("&account_type=")
                .concat(accountType);
        String res = wechatPayRequest.wechatHttpGet(url);
        log.info("查询退款订单结果：{}", res);
        Map<String, Object> resMap = JSONObject.parseObject(res, new TypeReference<Map<String, Object>>() {
        });
        return resMap.get("download_url").toString();
    }

    @ApiOperation(value = "下载账单")
    @GetMapping("/downloadBill")
    public void downloadBill(String downloadUrl) {
        log.info("下载账单，下载地址：{}", downloadUrl);
        HttpGet httpGet = new HttpGet(downloadUrl);
        httpGet.addHeader("Accept", "application/json");
        CloseableHttpResponse response = null;
        try {
            //使用wxPayClient发送请求得到响应
            response = wxPayNoSignClient.execute(httpGet);
            String body = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200 || statusCode == 204) {
                log.info("下载账单，返回结果 = " + body);
            } else {
                throw new ServiceException("下载账单异常, 响应码 = " + statusCode + ", 下载账单返回结果 = " + body);
            }
            // TODO 将body内容转为excel存入本地或者输出到浏览器，演示存入本地
            writeStringToFile(body);
        } catch (Exception e) {
            throw new ServiceException(e.getMessage());
        } finally {
            if (response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void writeStringToFile(String body) {
        FileWriter fw = null;
        try {
            String filePath = "D:\\Desktop\\wxPay.txt";
            fw = new FileWriter(filePath, true);
            BufferedWriter bw = new BufferedWriter(fw);
            bw.write(body);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


}
